/* Copyright (c) 2015,2016 Lucky Byte, Inc.
 */
package com.lucky_byte.pay.jar.lualib;

import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.util.encoders.Hex;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.Varargs;
import org.luaj.vm2.lib.TwoArgFunction;
import org.luaj.vm2.lib.VarArgFunction;
import org.luaj.vm2.lib.ZeroArgFunction;

import com.lucky_byte.pay.jar.lualib.LuaPacket;
import com.lucky_byte.pay.jar.p091.Fowder091;
import com.lucky_byte.pay.jar.p091.Lib091;
import com.lucky_byte.pay.jar.p091.Packet091;

/**
 * POS 009.1 Lua 模块
 */
public class LuaLib091 extends TwoArgFunction
{
	private static final Logger logger = LogManager.getLogger();

	@Override
	public LuaValue call(LuaValue modname, LuaValue env) {
		LuaValue library = tableOf();
		env.set("lib091", library);

		library.set("_new_packet", new NewPacket());
		library.set("_forward", new Forward());
		library.set("_gen_work_keys", new GenWorkKeys());
		library.set("_verify_mac", new VerifyMAC());
		library.set("_set_mac", new SetMAC());
		return library;
	}

	/**
	 * 创建一个新的空白报文
	 */
	class NewPacket extends ZeroArgFunction
	{
		@Override
		public LuaValue call() {
			Packet091 packet = new Packet091();
			return new LuaPacket(packet).call();
		}
	}

	/**
	 * 转发报文，报文可能来自于终端，或者由平台自己构造，之间有一些细微的区别:
	 *
	 * 对于平台构造的报文，转发时总是需要使用 packBytes() 方法来重新生成网络字节流，
	 * 因此这需要终端主密钥和工作密钥的支持。
	 *
	 * 如果是转发来自 POS 终端的报文，那么需要进一步区分，如果是透传模式，
	 * 也就是说直接将终端发送的报文转发到银联，这种情况下，不能通过packBytes()对
	 * 报文进行重组，因为重组后的字节流有可能和终端发送的字节流不一样，另外重组需要
	 * 密钥的支持。
	 * 如果是间连模式，那么就需要使用 packBytes() 进行报文重组，这和上面提到的
	 * 转发平台构造的报文类似。
	 */
	class Forward extends VarArgFunction
	{
		@Override
		public Varargs invoke(Varargs args) {
			LuaTable lua_packet = args.checktable(1);
			Packet091 packet = (Packet091) lua_packet.get("_object")
					.checkuserdata(Packet091.class);

			boolean indirect = args.optboolean(2, true);

			Fowder091 fowder = new Fowder091();
			Packet091 respkt = fowder.forward(packet, indirect);

			// 通过一个 Lua table 返回多个值
			LuaTable retval = LuaValue.tableOf();
			retval.set("netinfo",
					LuaValue.valueOf(fowder.getNetinfo().toString()));
			if (respkt != null) {
				retval.set("respkt", new LuaPacket(respkt).call());
			}
			return retval;
		}
	}

	/**
	 * 生成工作密钥
	 * 
	 * 参数:
	 *   1. 待转发的报文，必须是 Packet091 对象
	 *   2. 终端主密钥
	 */
	class GenWorkKeys extends VarArgFunction
	{
		@Override
		public Varargs invoke(Varargs args) {
			LuaTable lua_packet = args.checktable(1);
			Packet091 packet = (Packet091) lua_packet.get("_object")
					.checkuserdata(Packet091.class);
			String tmk_cipher = args.checkjstring(2);

			// 60.3 域指定终端签到密钥算法
			String f60 = packet.get("60");
			if (f60.length() < 11) {
				logger.error("签到报文 60 域长度不足，请检查.");
				return LuaValue.NIL;
			}
			// 根据不同的算法返回不同的密钥
			String micode = f60.substring(8, 11);
			if (!micode.equals("001") && !micode.equals("003") &&
					!micode.equals("004")) {
				logger.error("终端签到报文 60.3 域[{}]无效.", micode);
				return LuaValue.NIL;
			}
			Map<String, byte[]> work_keys =
					Lib091.genWorkKeys(micode, tmk_cipher);
			if (work_keys == null) {
				logger.error("生成工作密钥失败.");
				return LuaValue.NIL;
			}
			LuaTable result = LuaValue.tableOf();
			for (String k : work_keys.keySet()) {
				byte[] value = Hex.encode(work_keys.get(k));
				result.set(k, LuaValue.valueOf(new String(value)));
			}
			return result;
		}

	}

	/**
	 * 验证报文 MAC
	 */
	class VerifyMAC extends VarArgFunction
	{
		@Override
		public Varargs invoke(Varargs args) {
			LuaTable lua_packet = args.checktable(1);
			Packet091 packet = (Packet091) lua_packet.get("_object")
					.checkuserdata(Packet091.class);

			String tmk_cipher = args.checkjstring(2);
			String mak_cipher = args.checkjstring(3);
			int mac_algor = args.checkint(4);

			return LuaValue.valueOf(Lib091.verifyMAC(packet,
					tmk_cipher, mak_cipher, mac_algor));
		}
	}

	/**
	 * 设置报文 MAC
	 */
	class SetMAC extends VarArgFunction
	{
		@Override
		public Varargs invoke(Varargs args) {
			LuaTable lua_packet = args.checktable(1);
			Packet091 packet = (Packet091) lua_packet.get("_object")
					.checkuserdata(Packet091.class);
			String tmk_cipher = args.checkjstring(2);
			String mak_cipher = args.checkjstring(3);
			int mac_algor = args.checkint(4);

			return LuaValue.valueOf(Lib091.setMAC(packet,
					tmk_cipher, mak_cipher, mac_algor));
		}
	}

}
